using System;
using System.Collections.Generic;
using System.Collections;
using ValidationFramework.Extensions;
using ValidationFramework.Reflection;

namespace ValidationFramework.Specifications
{
	/// <summary>
	/// Performs a length validation in <see cref="ICollection"/>s.
	/// </summary>
	/// <remarks>If the value being validated is null the rule will evaluate to true.</remarks>
	/// <remarks>If <see cref="ExcludeDuplicatesFromCount"/> is true then <see cref="object.GetHashCode"/> is used to discard duplicates from the count. If the collection is null <see langword="true"/> will be returned. To validate for nulls use a <see cref="RequiredObjectRuleAttribute"/>.</remarks>
	/// <seealso cref="LengthCollectionRuleConfigReader"/>
	/// <seealso cref="LengthCollectionRuleAttribute"/>
#if (!SILVERLIGHT)
	[Serializable]
#endif
	public class HasCountInRangeSpec : LengthInRangeBase
	{
		// --- Fields

		private static readonly Type collectionType = typeof(ICollection);
		private static readonly Type genericCollectionType = typeof(ICollection<>);


		// --- Constructor

		/// <summary>
		/// Initializes a new, empty instance of the <see cref="EnumerableDuplicateRule"/> class using the specified <see cref="IEqualityComparer"/> object. 
		/// </summary>
		/// <remarks>
		/// The following are defaulted
		/// <list type="bullet">
		/// <item>
		/// <see cref="Rule.ErrorMessage"/> to the default error message.
		/// </item>
		/// <item>
		/// <see cref="Comparer"/> to null.
		/// </item>
		/// <item>
		/// <see cref="ExcludeDuplicatesFromCount"/> to false.
		/// </item>
		/// </list>
		/// </remarks>
		/// <param name="maximum">The maximum length allowed.</param>
		/// <param name="minimum">The minimum length allowed.</param>
		/// <exception cref="ArgumentNullException"><paramref name="minimum"/> is less than 0.</exception>
		/// <exception cref="ArgumentNullException"><paramref name="maximum"/> is not greater than or equal to <paramref name="minimum"/>.</exception>
		public HasCountInRangeSpec(int minimum, int maximum)
			: base(minimum, maximum)
		{
			CountDuplicates = false;
			Comparer = null;
		}


		///// <summary>
		///// Initializes a new, empty instance of the <see cref="EnumerableDuplicateRule"/> class using the specified <see cref="IEqualityComparer"/> object. 
		///// </summary>
		///// <param name="errorMessage">The error message for this rule. Pass a null to use the  default value.</param>
		///// <param name="maximum">The maximum length allowed.</param>
		///// <param name="minimum">The minimum length allowed.</param>
		///// <param name="useErrorMessageProvider"><c>true</c> to use <see cref="ConfigurationService.ErrorMessageProvider"/> when determining the error message for this <see cref="Rule"/>; otherwise <c>false</c>.</param>
		///// <param name="excludeDuplicatesFromCount">Indicates whether to exclude duplicate items from the count. Setting this to <see langword="true"/> will decrease the performance of <see cref="Validate"/></param>
		///// <param name="comparer">The <see cref="IEqualityComparer"/> object that defines how to compare objects for equality.
		///// -or- 
		///// a null reference to use the default hash code provider and the default comparer. The default comparer is each item's implementation of Object.Equals. 
		///// </param>
		///// <exception cref="ArgumentException"><paramref name="errorMessage"/> is a <see cref="string.Empty"/>.</exception>
		///// <exception cref="ArgumentNullException"><paramref name="minimum"/> is less than 0.</exception>
		///// <exception cref="ArgumentNullException"><paramref name="maximum"/> is not greater than or equal to <paramref name="minimum"/>.</exception>
		//public LengthCollectionSpecification(int minimum, int maximum, bool excludeDuplicatesFromCount, IEqualityComparer comparer)
		//    : base(minimum, maximum)
		//{
		//    ExcludeDuplicatesFromCount = excludeDuplicatesFromCount;
		//    Comparer = comparer;
		//}


		// --- Properties


		/// <summary>
		/// Gets whether to exclude duplicates when calculating the length.
		/// </summary>
		public bool CountDuplicates
		{
			get;
			set;
		}

		/// <summary>
		/// Gets the <see cref="IEqualityComparer"/> for the <see cref="LengthCollectionRule"/>.
		/// </summary>
		public IEqualityComparer Comparer
		{
			get;
			set;
		}


		// --- Methods
		private static int GetCount(object targetMemberValue)
		{
			//You cant do this till you have a not null value.
			var iCollection = targetMemberValue as ICollection;
			if (iCollection == null)
			{
				//Dont cache countPropertyInfo as most classes that implement ICollection<> also implement ICollection so it should usually not get here
				var countPropertyInfo = targetMemberValue.GetType().GetProperty("Count");
				return (int)countPropertyInfo.GetValue(targetMemberValue, null);
			}
			else
			{
				return iCollection.Count;
			}
		}


		private int GetCountExcludeDuplicates(object targetMemberValue)
		{
			var enumerable = (IEnumerable)targetMemberValue;
			var list = new List<object>();
			if (Comparer == null)
			{
				foreach (var enumerableValue in enumerable)
				{
					var found = false;
					foreach (var listValue in list)
					{
						if (Equals(enumerableValue, listValue))
						{
							found = true;
						}
					}
					if (!found)
					{
						list.Add(enumerableValue);
					}
				}
			}
			else
			{
				foreach (var enumerableValue in enumerable)
				{
					var found = false;
					foreach (var listValue in list)
					{
						if (Comparer.Equals(enumerableValue, listValue))
						{
							found = true;
						}
					}
					if (!found)
					{
						list.Add(enumerableValue);
					}
				}
			}

			return list.Count;
		}


		// --- Methods | Overrides	
		/// <inheritdoc />
		public override bool CanBeSatisfiedBy(Type typeToCheck)
		{
			var isGenericICollection = genericCollectionType.IsAssignableFrom(typeToCheck, true);
			var isICollection = collectionType.IsAssignableFrom(typeToCheck, true);

			return isICollection || isGenericICollection;
		}

		/// <inheritdoc />
		public override string DefaultMessage(string tokenizedMemberName, string descriptorType)
		{
			if (Minimum == Maximum)
			{
				return string.Format("The number of items in the {0} '{1}' must be {2}.", descriptorType, tokenizedMemberName, Minimum);
			}
			else
			{
				return string.Format("The number of items in the {0} '{1}' must be between {2} and {3}.", descriptorType, tokenizedMemberName, Minimum, Maximum);
			}
		}

		/// <inheritdoc />
		public override string ToString()
		{
			if (Minimum == Maximum)
			{
				return string.Format("The collection must contain {0}.", Maximum);
			}
			else
			{
				return string.Format("The collection must contain between {0} and {1} items.", Minimum, Maximum);
			}
		}


		/// <inheritdoc />
		public override bool IsEquivalentTo(ISpecification spec)
		{
			var lcSpecification = (HasCountInRangeSpec)spec;
			return lcSpecification.Maximum == Maximum
				&& lcSpecification.Minimum == Minimum
				&& lcSpecification.Comparer == Comparer;
		}

		/// <inheritdoc />
		public override bool IsSatisfiedBy(object target)
		{
			if (target != null)
			{
				int count;
				if (CountDuplicates)
				{
					count = GetCount(target);
				}
				else
				{
					count = GetCountExcludeDuplicates(target);
				}
				return LengthValidationHelper.IsLengthValid(count, Minimum, Maximum);
			}

			return true;
		}
	}
}
